<?php

/**
 * 
 * @desc This class provides access to database tables
 * @see http://code.google.com/p/dabase
 * @author Barbushin Sergey http://www.linkedin.com/in/barbushin
 * 
 */
class DaBase_Getter {

	protected $db;
	protected $name;
	protected $tableName;
	protected $objectsClass;
	
	protected $filters;
	protected $limit;
	protected $offset;
	protected $orderBy;
	protected $groupBy;
	
	protected $appenders;
	
	const objectsClass = null;

	public function __construct(DaBase_Db $db, $tableName, $objectsClass, $getterName=null) {
		$this->db = $db;
		$this->tableName = $tableName;
		$this->objectsClass = $objectsClass;
		$this->name = $getterName ? $getterName : $tableName;
		$this->postInit();
	}

	protected function postInit() {
	}

	public function getDb() {
		return $this->db;
	}

	public function getName() {
		return $this->name;
	}

	public function getTable() {
		return $this->tableName;
	}

	public function reset() {
		$this->filters = null;
		$this->limit = null;
		$this->offset = null;
		$this->orderBy = null;
		$this->appenders = null;
		return $this;
	}

	/**************************************************************
	 CRUD
	 **************************************************************/
	
	public function getNew(array $properties = array()) {
		return new $this->objectsClass($properties, $this);
	}

	public function get($oneObject = false) {
		return $this->getByPreparedQuery($this->db->sql('SELECT * FROM ' . $this->db->quoteName($this->tableName) . $this->sqlWhere() . $this->sqlGroupBy() . $this->sqlOrderBy() . $this->sqlLimit()), $oneObject);
	}

	public function getByQuery($prepareSql) {
		$replacers = array_slice(func_get_args(), 1);
		return $this->getByPreparedQuery($this->db->prepareSql($prepareSql, $replacers));
	}

	public function getOneByQuery($prepareSql) {
		$replacers = array_slice(func_get_args(), 1);
		return $this->getByPreparedQuery($this->db->prepareSql($prepareSql, $replacers), true);
	}

	protected function getByPreparedQuery($preparedSql, $oneObject = false) {
		$objects = array();
		foreach ($this->db->fetchPreparedSql($preparedSql) as $i => $row) {
			$objects[isset($row['id']) ? $row['id'] : $i] = $this->getNew($row);
		}
		
		$this->handleAppendersToObjects($objects);
		
		if ($oneObject) {
			if (count($objects) > 1) {
				throw new DaBase_Exception('Request returns more than one object');
			}
			return reset($objects);
		}
		return $objects;
	}

	public function getObjectById($id) {
		if ($objects = $this->filter('id', $id)->get()) {
			return reset($objects);
		}
		else {
			throw new DaBase_ObjectNotFound($this, $id);
		}
	}

	public function getColumn($valueProperty, $keyProperty = null) {
		$founds = array();
		if($keyProperty) {
			foreach ($this->db->fetch('SELECT ' . $this->db->quoteName($keyProperty) . ' as `key`, ' . $this->db->quoteName($valueProperty).' as `value` FROM ' . $this->db->quoteName($this->tableName) . $this->sqlWhere() . $this->sqlOrderBy() . $this->sqlLimit()) as $row) {
				$founds[$row['key']] = $row['value'];
			}
		}
		else {
			foreach ($this->db->fetch('SELECT ' . $this->db->quoteName($valueProperty) . ' as `value` FROM ' . $this->db->quoteName($this->tableName) . $this->sqlWhere() . $this->sqlOrderBy() . $this->sqlLimit()) as $i => $row) {
				$founds[$i] = $row['value'];
			}
		}
		
		return $founds;
	}

	public function count() {
		return (int)$this->db->fetchCell('SELECT COUNT(*) FROM ' . $this->db->quoteName($this->tableName) . $this->sqlWhere());
	}

	public function update(array $propertiesValues) {
		$this->db->query('UPDATE ' . $this->db->quoteName($this->tableName) . ' SET ' . implode(', ', $this->db->quoteEquals($propertiesValues)) . $this->sqlWhere() . $this->sqlOrderBy() . $this->sqlLimit());
		return $this;
	}

	public function delete() {
		$this->db->query('DELETE FROM ' . $this->db->quoteName($this->tableName) . $this->sqlWhere() . $this->sqlOrderBy() . $this->sqlLimit());
		return $this;
	}

	/**************************************************************
	 APPENDING
	 **************************************************************/
	
	public function append(DaBase_Getter $getter, $appendProperty = null, $joinProperty = null, $multiple = true) {
		if (!$appendProperty) {
			$appendProperty = $getter->getName();
		}
		if (!$joinProperty) {
			$joinProperty = $this->db->getRouter()->getJoinFieldNameByGetter($this);
		}
		if (isset($this->appenders[$appendProperty])) {
			throw new DaBase_Exception('Property with name "' . $appendProperty . '" already appended');
		}
		$this->appenders[$appendProperty] = array($getter, $appendProperty, $joinProperty, $multiple);
		return $this;
	}

	protected function handleAppendersToObjects(array $objects) {
		if ($this->appenders) {
			$ids = array_keys($objects);
			foreach ($this->appenders as $appender) {
				list($getter, $appendProperty, $joinProperty, $multiple) = $appender;
				if ($getter->getLimit() || $getter->getOffset()) {
					throw new DaBase_Exception('Cannot append getter with not empty limit or offset');
				}
				foreach ($objects as $object) {
					$object->$appendProperty = array();
				}
				foreach ($getter->filter($joinProperty, $ids)->get() as $id => $appendObject) {
					if($multiple) {
						$objects[$appendObject->$joinProperty]->{$appendProperty}[$id] = $appendObject;
					}
					else {
						$objects[$appendObject->$joinProperty]->$appendProperty = $appendObject;
					}
				}
			}
		}
	}

	/**************************************************************
	 GETTER SETTINGS
	 **************************************************************/
	
	public function filter($property, $value, $operand = '=') {
	if($operand == 1) {
	var_dump(debug_backtrace());
	}
		if (is_array($value)) {
			$sql = $this->db->quoteName($property) . ' IN (' . implode(',', $this->db->quoteArray($value)) . ')';
			$this->filters[] = $value ? $sql : '0 /* '.$sql.' */';
		}
		else {
			$this->filters[] = $this->db->quoteName($property) . $operand . $this->db->quote($value);
		}
		return $this;
	}

	protected function sqlWhere() {
		if ($this->filters) {
			return ' WHERE ' . implode(' AND ', $this->filters);
		}
		return '';
	}

	public function orderBy($property, $desc = false) {
		$this->orderBy[] = $this->db->quoteName($property) . ($desc ? ' DESC' : '');
		return $this;
	}

	public function orderByRand() {
		$this->orderBy[] = 'RAND()';
		return $this;
	}

	protected function sqlOrderBy() {
		if ($this->orderBy) {
			return ' ORDER BY ' . implode(', ', $this->orderBy);
		} 
		return '';
	}

	public function groupBy($property, $desc = false) {
		$this->groupBy[] = $this->db->quoteName($property) . ($desc ? ' DESC' : '');
		return $this;
	}

	protected function sqlGroupBy() {
		if ($this->groupBy) {
			return ' GROUP BY ' . implode(', ', $this->groupBy);
		}
		return '';
	}

	public function limit($limit, $offset = null) {
		$this->limit = $limit;
		$this->offset = $offset;
		return $this;
	}

	public function limitPage($itemsOnPage, $page = 1) {
		return $this->limit($itemsOnPage, $itemsOnPage * ($page - 1));
	}

	public function getLimit() {
		return $this->limit;
	}

	public function getOffset() {
		return $this->offset;
	}

	protected function sqlLimit() {
		if ($this->limit || $this->offset) {
			return ' LIMIT ' . (int)$this->limit . ($this->offset ? ', ' . (int)$this->offset : '');
		}
		return '';
	}

	/**************************************************************
	 MAGICS
	 **************************************************************/
	
	public function __toString() {
		return $this->tableName;
	}

	public function __get($property) {
		return $this->append($this->db->getGetter($property));
	}

	public function __set($property, $value) {
		return $this->update(array($property => $value));
	}

	public function __call($method, $attributes) {
		if (!isset($attributes[0])) {
			throw new DaBase_Exception('Unkown method "' . $method . '" requested');
		}
		return $this->filter($method, $attributes[0], count($attributes) > 1 ? $attributes[1] : '=');
	}

	public function __invoke($value) {
		return $this->getObjectById($value);
	}
}

/**
 * 
 * @desc This is exception of unkown objects requests
 * @author Barbushin Sergey http://www.linkedin.com/in/barbushin
 * 
 */
class DaBase_ObjectNotFound extends DaBase_Exception {
	
	protected $getter;
	protected $objectId;

	public function __construct(DaBase_Getter $getter, $objectId) {
		$this->getter = $getter;
		$this->objectId = $objectId;
		parent::__construct('Object with id "' . $objectId . '" not found in table "' . $getter->getTable() . '"');
	}
}